#include <assert.h>
#include <math.h>
#include <stdarg.h>

#include "s21_string.h"

/* Алгоритм:
Шаг 1) - парсинг флагов и проставление заготовок для численных типов
Шаг 2) - выбор обработчика (главным образом зависит от возвращаемого типа)
Шаг 3) - парсинг. Тупой и прямолинейный.
Важно:
1) Всегда возвращать сообщение, успешен ли парсинг
2) Всегда проверять наличие следующего символа
*/

int s21_sscanf(const char *str, const char *format, ...) {
  // init
  void *blank_ptr;
  double blank_dbl;
  long double blank_ldb;
  int blank_int;
  short blank_sht;
  long blank_lng;
  char blank_str[512];  // TODO?

  va_list args;
  va_start(args, format);

  int format_ptr = 0;
  int str_ptr = 0;
  int counter = 0;
  int responce = 1;
  while (responce > 0) {
    responce = 0;
    if (format[format_ptr] != '%') {
      if (format[format_ptr] == str[str_ptr] && format[format_ptr] &&
          str[str_ptr]) {
        str_ptr++;
        format_ptr++;
        responce++;
      } else if (str[str_ptr] == ' ') {
        str_ptr++;
        responce++;
      } else if (format[format_ptr] == ' ') {
        format_ptr++;
        responce++;
      }
    } else {
      format_ptr++;
      s21_s_prot prot = parse_scan_prototype(format, &format_ptr);
      // Integers
      if (s21_strchr("idoxuX", prot.specf)) {  // numbers in different types
        if (prot.add_specf == 'h') {
          responce = s21_scan_short(
              str, &str_ptr, prot,
              (prot.ignore) ? &blank_sht : (short *)va_arg(args, short *));
        } else if (prot.add_specf == 'l') {
          responce =
              s21_scan_long(str, &str_ptr, prot,
                            (prot.ignore) ? &blank_lng : va_arg(args, long *));
        } else {
          responce =
              s21_scan_int(str, &str_ptr, prot,
                           (prot.ignore) ? &blank_int : va_arg(args, int *));
        }
        // Float
      } else if (s21_strchr("eEfgG", prot.specf)) {
        if (prot.add_specf != 'L') {
          responce = s21_scan_double(
              str, &str_ptr,
              (prot.ignore) ? &blank_dbl : va_arg(args, double *));
        } else {
          responce = s21_scan_long_double(
              str, &str_ptr,
              (prot.ignore) ? &blank_ldb : va_arg(args, long double *));
        }

      } else if (prot.specf == 'c') {
        if (str[str_ptr]) {
          *va_arg(args, char *) = str[str_ptr];
          responce = 1;
        }
      } else if (prot.specf == '%') {
        if (str[str_ptr] == '%') responce = 1;
      } else if (prot.specf == 's') {
        responce = s21_scan_str(
            str, &str_ptr, (prot.ignore) ? blank_str : va_arg(args, char *));
      } else if (prot.specf == 'p') {
        responce = s21_scan_ptr(
            str, &str_ptr, (prot.ignore) ? &blank_ptr : va_arg(args, void **));
      } else if (prot.specf == 'n') {
        *va_arg(args, int *) = str_ptr;
        responce = 1;
        counter--;
      } else {
        return 0;  // ERROR
      }
      counter += (responce > 0) ? 1 : 0;
    }
  }
  va_end(args);
  return counter;
}

s21_s_prot parse_scan_prototype(const char *format, int *format_ptr) {
  s21_s_prot result = {0, '/', '/'};

  // ignore
  if (format[*format_ptr] == '*') {
    result.ignore = 1;
    *format_ptr += 1;
  }

  // specification
  if (s21_strchr("hlL", format[*format_ptr]) && format[*format_ptr] &&
      format[*format_ptr]) {
    result.add_specf = format[*format_ptr];
    *format_ptr += 1;
  }
  assert(s21_strchr("cdieEfgGosuxXpn%", format[*format_ptr]) &&
         format[*format_ptr] && format[*format_ptr]);
  result.specf = format[*format_ptr];
  *format_ptr += 1;

  return result;
}

#define SC_INT(type)                                                           \
  int s21_scan_##type(const char *str, int *str_ptr, s21_s_prot prot,          \
                      type *num) {                                             \
    *num = 0;                                                                  \
    int result = 1, sign = 1, base = 10;                                       \
    int str_ptr_at_start = *str_ptr;                                           \
    while (str[*str_ptr] == ' ' || str[*str_ptr] == '\n') *str_ptr += 1;       \
                                                                               \
    if (prot.specf == 'x' || prot.specf == 'X') {                              \
      base = 16;                                                               \
      if ((str[*str_ptr] == '0') &&                                            \
          (str[*str_ptr + 1] == 'x' || str[*str_ptr + 1] == 'X'))              \
        *str_ptr += 2;                                                         \
    } else if (prot.specf == 'o') {                                            \
      base = 8;                                                                \
      if ((str[*str_ptr] == '0') && str[*str_ptr + 1] >= '0' &&                \
          str[*str_ptr + 1] <= '9')                                            \
        *str_ptr += 1;                                                         \
    } else if (prot.specf == 'd' && s21_strchr("+-", str[*str_ptr])) {         \
      sign = (str[*str_ptr] == '+') ? 1 : -1;                                  \
      *str_ptr += 1;                                                           \
    } else if (prot.specf == 'i') {                                            \
      if ((str[*str_ptr] == '-' || str[*str_ptr] == '+')) {                    \
        sign = (str[*str_ptr] == '+') ? 1 : -1;                                \
        *str_ptr += 1;                                                         \
      } else if ((str[*str_ptr] == '0') &&                                     \
                 (str[*str_ptr + 1] == 'x' || str[*str_ptr + 1] == 'X')) {     \
        base = 16;                                                             \
        *str_ptr += 2;                                                         \
      } else if ((str[*str_ptr] == '0') &&                                     \
                 (str[*str_ptr + 1] >= '0' && str[*str_ptr + 1] <= '9')) {     \
        base = 8;                                                              \
        *str_ptr += 1;                                                         \
      }                                                                        \
    }                                                                          \
                                                                               \
    if (base == 16) {                                                          \
      /* Как минимум одно вхождение */                  \
      if (!((str[*str_ptr] >= '0' && str[*str_ptr] <= '9') ||                  \
            (str[*str_ptr] >= 'a' && str[*str_ptr] <= 'f') ||                  \
            (str[*str_ptr] >= 'A' && str[*str_ptr] <= 'F')))                   \
        result = 0;                                                            \
                                                                               \
      while ((str[*str_ptr] >= 48 && str[*str_ptr] < 58) ||                    \
             (str[*str_ptr] >= 65 && str[*str_ptr] < 71) ||                    \
             (str[*str_ptr] >= 97 && str[*str_ptr] < 103)) {                   \
        if (str[*str_ptr] >= 65 && str[*str_ptr] < 71) {                       \
          *num = *num * base + 10 + (str[*str_ptr] - 65);                      \
        } else if (str[*str_ptr] >= 97 && str[*str_ptr] < 103) {               \
          *num = *num * base + 10 + (str[*str_ptr] - 97);                      \
        } else {                                                               \
          *num = *num * base + (str[*str_ptr] - 48);                           \
        }                                                                      \
        *str_ptr += 1;                                                         \
      }                                                                        \
    } else {                                                                   \
      if (!(str[*str_ptr] >= 48 && str[*str_ptr] < ((base == 8) ? 56 : 58))) { \
        result = 0;                                                            \
      }                                                                        \
      while (str[*str_ptr] >= 48 && str[*str_ptr] < ((base == 8) ? 56 : 58)) { \
        *num = *num * base + (str[*str_ptr] - 48);                             \
        *str_ptr += 1;                                                         \
      }                                                                        \
    }                                                                          \
    *num *= sign;                                                              \
    return (*str_ptr - str_ptr_at_start) * result;                             \
  }

SC_INT(short)
SC_INT(int)
SC_INT(long)

#define SC_FL(name, type)                                                \
  int s21_scan_##name(const char *str, int *str_ptr, type *num) {        \
    int sign = 1;                                                        \
    int result = 1;                                                      \
    int str_ptr_at_start = *str_ptr;                                     \
    while (str[*str_ptr] == ' ' || str[*str_ptr] == '\n') *str_ptr += 1; \
    if (str[*str_ptr] == '+' || str[*str_ptr] == '-') {                  \
      sign = (str[*str_ptr] == '+') ? 1 : -1;                            \
      *str_ptr += 1;                                                     \
    }                                                                    \
                                                                         \
    if (!(str[*str_ptr] >= 48 && str[*str_ptr] < 58)) {                  \
      return -1;                                                         \
    }                                                                    \
    *num = str[*str_ptr] - 48;                                           \
    *str_ptr += 1;                                                       \
                                                                         \
    while (str[*str_ptr] >= 48 && str[*str_ptr] < 58) {                  \
      *num = *num * 10 + (str[*str_ptr] - 48);                           \
      *str_ptr += 1;                                                     \
    }                                                                    \
                                                                         \
    if (str[*str_ptr] == '.') {                                          \
      *str_ptr += 1;                                                     \
      int frac_counter = 1;                                              \
      while (str[*str_ptr] >= 48 && str[*str_ptr] < 58) {                \
        *num += (str[*str_ptr] - 48) * pow(10, -frac_counter);           \
        *str_ptr += 1, frac_counter++;                                   \
      }                                                                  \
    }                                                                    \
                                                                         \
    if ((str[*str_ptr] == 'e' || str[*str_ptr] == 'E') &&                \
        (str[*str_ptr + 1] == '+' || str[*str_ptr + 1] == '-')) {        \
      int pow_sign = (str[*str_ptr + 1] == '+') ? 1 : -1;                \
      int power = 0;                                                     \
      *str_ptr += 2;                                                     \
                                                                         \
      while (str[*str_ptr] >= 48 && str[*str_ptr] < 58) {                \
        power = power * 10 + (str[*str_ptr] - 48);                       \
        *str_ptr += 1;                                                   \
      }                                                                  \
      *num *= pow(10, pow_sign * power);                                 \
    }                                                                    \
                                                                         \
    *num *= sign;                                                        \
    return (*str_ptr - str_ptr_at_start) * result;                       \
  }

SC_FL(long_double, long double);
SC_FL(double, double);

int s21_scan_ptr(const char *str, int *str_ptr, void **ptr) {
  unsigned long num = 0;
  int result = 1, base = 16;
  int str_ptr_at_start = *str_ptr;

  while (str[*str_ptr] == ' ' || str[*str_ptr] == '\n') *str_ptr += 1;
  if (!(str[*str_ptr] == '0' && str[*str_ptr + 1] == 'x')) return 0;
  *str_ptr += 2;
  if (!((str[*str_ptr] >= '0' && str[*str_ptr] <= '9') ||
        (str[*str_ptr] >= 'a' && str[*str_ptr] <= 'f') ||
        (str[*str_ptr] >= 'A' && str[*str_ptr] <= 'F')))
    result = 0;

  while ((str[*str_ptr] >= 48 && str[*str_ptr] < 58) ||
         (str[*str_ptr] >= 65 && str[*str_ptr] < 71) ||
         (str[*str_ptr] >= 97 && str[*str_ptr] < 103)) {
    if (str[*str_ptr] >= 65 && str[*str_ptr] < 71) {
      num = num * base + 10 + (str[*str_ptr] - 65);
    } else if (str[*str_ptr] >= 97 && str[*str_ptr] < 103) {
      num = num * base + 10 + (str[*str_ptr] - 97);
    } else {
      num = num * base + (str[*str_ptr] - 48);
    }
    *str_ptr += 1;
  }
  *ptr = (void *)num;
  return (*str_ptr - str_ptr_at_start) * result;
}

int s21_scan_str(const char *str, int *str_ptr, char *dest) {
  int str_ptr_at_start = *str_ptr;
  while (str[*str_ptr] == ' ' || str[*str_ptr] == '\n') *str_ptr += 1;
  while (str[*str_ptr] && str[*str_ptr] != ' ') {
    *dest = str[*str_ptr];
    dest += 1, *str_ptr += 1;
  }
  *dest = '\0';
  return *str_ptr - str_ptr_at_start;
}